package my.spring.servlet;

import com.google.gson.Gson;
import my.project.dao.UserDao;
import my.project.util.Log;
import my.spring.annotation.*;
import my.spring.db.DaoProxy;
import my.spring.db.DbException;
import my.spring.db.MySQL;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 框架的Ioc核心
 */
public class DispatcherServlet extends HttpServlet {
    // 扫描包后的类集合
    private final Set<Class<?>> classes = new HashSet<>();
    // 类集合中，有bean注解的
    private final HashMap<String, Object> beans = new HashMap<>();
    // 请求路径和执行方法的映射
    private final HashMap<String, Method> mappings = new HashMap<>();

    @Override
    public void init() throws ServletException {
        Log.log("DispatcherServlet init...");
        ServletConfig config = getServletConfig();
        MySQL.test(config.getInitParameter("driverClassName"),
                config.getInitParameter("url"),
                config.getInitParameter("username"),
                config.getInitParameter("password"));
        String packageName = config.getInitParameter("scanPackage");
        scanPackage(packageName, null);
        filterBeans();
        autowire();
        mapping();
    }

    // 映射
    private void mapping() {
        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            Object bean = entry.getValue();
            Class<?> clazz = bean.getClass();
            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping rm1 = clazz.getAnnotation(RequestMapping.class);
                String classPath = rm1.value(); // Controller类上面的请求路径
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping rm2 = method.getAnnotation(RequestMapping.class);
                        String methodPath = rm2.value(); // 方法上面的请求路径
                        String reqPath = classPath + methodPath;
                        Log.log("create mapping: {} => {}", reqPath, method);
                        mappings.put(reqPath, method);
                    }
                }
            }
        }
    }

    // 注入
    private void autowire() {
        try {
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
                Object bean = entry.getValue();
                Class<?> clazz = bean.getClass();
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(Autowired.class)) {
                        Class<?> targetBeanClass = field.getType();
                        String targetBeanName = genBeanKey(targetBeanClass);
                        Object targetBean = beans.get(targetBeanName);
                        Log.log("autowire bean: {} => {}", targetBean, bean);
                        field.setAccessible(true); // 属性是private，需要打开权限
                        field.set(bean, targetBean); // 注入bean
                    } else if (field.isAnnotationPresent(Resource.class)) {
                        Class<?> targetBeanClass = field.getType();
                        String targetBeanName = genBeanKey(targetBeanClass);
                        Object proxy = beans.get(targetBeanName);
                        Log.log("autowire proxy: {} => {}", proxy, bean);
                        field.setAccessible(true); // 属性是private，需要打开权限
                        field.set(bean, proxy); // 注入bean
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 过滤出bean
    private void filterBeans() {
        try {
            for (Class<?> clazz : classes) {
                String name = genBeanKey(clazz);
                if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {
                    // 普通业务Bean
                    Object bean = clazz.newInstance();
                    Log.log("create bean: {} => {}", name, bean);
                    beans.put(name, bean);
                } else if (clazz.isAnnotationPresent(Repository.class)) {
                    // 数据库操作接口
                    UserDao proxy = (UserDao) DaoProxy.createProxy(clazz);
                    Log.log("dao proxy: {} => {}", name, proxy);
                    beans.put(name, proxy);
                } else if (clazz.isAnnotationPresent(Entity.class)) {
                    // TODO 数据库实体
                    Log.log("model entity: {}", name);
                    Entity e = clazz.getAnnotation(Entity.class);
                    String sql = e.value();
                    MySQL.executeUpdate(sql, null);
                }
            }
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        } catch (DbException e) {
            Log.log("数据库建表失败，" + e.getMessage());
            e.printStackTrace();
        }
    }

    // 生成Bean类的key UserService => userService
    private static String genBeanKey(Class<?> clazz) {
        return String.valueOf(clazz.getSimpleName().charAt(0)).toLowerCase() + clazz.getSimpleName().substring(1);
    }

    // 递归扫描包
    private void scanPackage(String packageName, String packagePath) {
        try {
            if (packagePath == null) {
                URL url = Thread.currentThread().getContextClassLoader().getResource(packageName.replaceAll("\\.", "/"));
                assert url != null;
                packagePath = url.getPath();
            }
            Log.log("scanPackage: {}", packagePath);
            File dir = new File(packagePath);
            File[] files = dir.listFiles();
            if (files != null) {
                for (File f : files) {
                    if (f.isDirectory()) {
                        scanPackage(packageName + "." + f.getName(), f.getPath());
                    } else {
                        if (f.getPath().endsWith(".class")) {
                            String className = packageName + "." + f.getName().substring(0, f.getName().length() - 6);
                            Log.log("findClass: {}", className);
                            Class<?> clazz = Class.forName(className);
                            classes.add(clazz);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 除了 doGet 和 doPost 还有：doHead、doPut、doDelete、doOptions、doTrace
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String reqPath = req.getRequestURI().substring(req.getContextPath().length());
        Method method = mappings.get(reqPath);
        if (method != null) {
            try {
                String beanKey = genBeanKey(method.getDeclaringClass());
                Object bean = beans.get(beanKey);
                Object[] args = handleArgs(req, method); // new Object[]{"liuzy", "123123"};
                Object ret = method.invoke(bean, args);
                respJson(resp, ret);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        } else {
            resp.sendError(404, "Not Found.");
        }
    }

    // 从请求中找出method方法需要的参数
    private Object[] handleArgs(HttpServletRequest req, Method method) {
        Parameter[] parameters = method.getParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            if (p.isAnnotationPresent(RequestParam.class)) {
                RequestParam rp = p.getAnnotation(RequestParam.class);
                String paramKey = rp.value();
                String paramVal = req.getParameter(paramKey); // 从请求参数中取值
                args[i] = paramVal;
            } else {
                args[i] = null;
            }
        }
        return args;
    }

    // 响应JSON数据
    private void respJson(HttpServletResponse resp, Object ret) throws IOException {
        if (!resp.isCommitted()) { // 在controller的方法中（或其他拦截器）没有写出返回值时
            resp.setStatus(200);
            resp.setContentType("application/json; charset=UTF-8");
            String json = new Gson().toJson(ret);
            resp.getWriter().write(json);
            resp.getWriter().flush();
        }
    }


    @Override
    public void destroy() {
        Log.log("DispatcherServlet destroy...");
    }
}
